import asyncio
import os
from random import randint

import aiofiles
import speedtest
from aiogram import Bot
from aiogram.types import BufferedInputFile, FSInputFile
from fluentogram import TranslatorRunner
from sqlalchemy.ext.asyncio import AsyncSession
from sqlalchemy.orm import sessionmaker

from app.bot.services.async_ssh_connector import (
    change_authorization_type,
    change_expired_proxies,
    change_rotation_type,
    delete_vpn_config,
    generate_vpn_config,
)
from app.bot.services.broadcaster import broadcast, send_document
from app.bot.services.date_time import format_datetime, get_date_time
from app.bot.services.helpers import get_expiring_on, get_server_proxies
from app.bot.services.locale import Locale, Localizator
from app.bot.services.loggers import apcheduler_logger as logger
from app.bot.services.proxy_checker import get_proxy_status
from app.bot.services.qr_generator import generate_qr_code
from app.config import Settings
from app.database.models import Proxy, Server
from app.database.services.allowed_ips import (
    add_ip_to_order,
    get_allowed_ip_by_order,
    get_ip,
    remove_ip_from_order,
)
from app.database.services.bot_settings import get_bot_settings
from app.database.services.orders.common import (
    get_expired_order_ids,
    get_expiring_orders,
    get_order,
)
from app.database.services.orders.proxy_orders import update_order_expiring
from app.database.services.proxy import (
    change_expired_proxy,
    get_all_proxies,
    get_proxy,
    update_authorization_type,
    update_rotation_period,
)
from app.database.services.stats import update_stats
from app.database.services.users import get_user, get_users


async def notify_users(
    bot: Bot,
    session: AsyncSession,
    config: Settings,
    text: str,
    text_eng: str,
    mailing_settings: str,
) -> None:

    if mailing_settings == "not_registered":
        users = await get_users(session, registered=False)
    elif mailing_settings == "registered":
        users = await get_users(session, registered=True)
    else:
        users = await get_users(session)

    messages_sent, errors_count = await broadcast(bot, users, text, text_eng)

    await broadcast(
        bot,
        config.tg_bot.admin_ids,
        (
            f"Рассылка закончена\n"
            f"Успешно отправлено сообщений: {messages_sent}\n"
            f"С ошибкой: {errors_count}\n"
            f"Всего пользователей: {len(users)}"
        ),
    )

    await update_stats(session, messages_sent=messages_sent)


async def autorenew_orders(
    bot: Bot, session_factory: sessionmaker, config: Settings, fluent: Localizator
) -> None:

    try:
        async with session_factory() as session:
            bot_settings = await get_bot_settings(session)
            if bot_settings and bot_settings.order_notify_status:
                orders = await get_expiring_orders(session, autorenew=True)
                for order in orders:
                    logger.info(f"Продлеваю заказ №{order.id}")
                    locale: TranslatorRunner = fluent.get_by_locale(
                        order.user.lang_code
                    )
                    extended_expiring_on = await get_expiring_on(
                        order.expiring_on, order.period.value
                    )
                    try:
                        await update_order_expiring(
                            session,
                            order.id,
                            extended_expiring_on,
                            order.summ,
                            order.user.telegram_id,
                        )
                        logger.info(
                            f"Автоматически продлил заказ №{order.id} до {extended_expiring_on}"
                        )
                        extended_expiring_on = format_datetime(extended_expiring_on)
                        if order.type == "subscription":
                            msg = locale.get(
                                "subscription-autorenew-success",
                                order_summ=order.summ,
                                extended_expiring_on=extended_expiring_on,
                            )
                        elif order.type == "vpn":
                            msg = locale.get(
                                "vpn-autorenew-success",
                                order_summ=order.summ,
                                extended_expiring_on=extended_expiring_on,
                            )
                        else:
                            msg = locale.get(
                                "order-autorenew-success",
                                order_id=str(order.id),
                                order_summ=order.summ,
                                extended_expiring_on=extended_expiring_on,
                            )
                        await broadcast(
                            bot,
                            [order.user.telegram_id],
                            msg,
                        )
                    except ValueError:
                        logger.info(
                            f"Недостаточно баланса для продления заказа/подписки №{order.id}"
                        )
                        if order.type == "subscription":
                            msg = locale.get(
                                "subscription-autorenew-not-enough-money",
                                order_summ=order.summ,
                            )
                        elif order.type == "vpn":
                            msg = locale.get(
                                "vpn-autorenew-not-enough-money",
                                order_summ=order.summ,
                            )
                        else:
                            msg = locale.get(
                                "order-autorenew-not-enough-money",
                                order_id=str(order.id),
                                order_summ=order.summ,
                            )
                        await broadcast(
                            bot,
                            [order.user.telegram_id],
                            msg,
                        )
                    except Exception as e:
                        logger.exception(e)
                        if order.type == "subscription":
                            msg = locale.get(
                                "subscription-autorenew-error", order_summ=order.summ
                            )
                        elif order.type == "vpn":
                            msg = locale.get(
                                "vpn-autorenew-error", order_summ=order.summ
                            )
                        else:
                            msg = locale.get(
                                "order-autorenew-error",
                                order_id=str(order.id),
                                order_summ=order.summ,
                            )
                        await broadcast(
                            bot,
                            [order.user.telegram_id],
                            msg,
                        )

    except Exception as e:
        logger.exception(e)
        await broadcast(
            bot,
            [config.tg_bot.developer_id],
            f"Ошибка во время автообновления заказа/подписки: {str(e)}",
        )


async def notify_expiring_orders(
    bot: Bot, session_factory: sessionmaker, config: Settings, fluent: Localizator
) -> None:
    try:
        async with session_factory() as session:
            bot_settings = await get_bot_settings(session)
            if bot_settings and bot_settings.order_notify_status:
                orders = await get_expiring_orders(session)
                for order in orders:
                    date = get_date_time(get_datetime=True)
                    locale: TranslatorRunner = fluent.get_by_locale(
                        order.user.lang_code
                    )
                    expiring_on = format_datetime(order.expiring_on)
                    if order.type == "subscription":
                        extend_msg = locale.get(
                            "subscription-expiring",
                            expiring_on=expiring_on,
                        )
                    elif order.type == "vpn":
                        extend_msg = locale.get(
                            "vpn-expiring",
                            expiring_on=expiring_on,
                        )
                    else:
                        extend_msg = locale.get(
                            "order-expiring",
                            order_id=str(order.id),
                            order_summ=order.summ,
                            expiring_on=expiring_on,
                        )

                    await broadcast(bot, [order.user.telegram_id], extend_msg)
                    order.last_notified = date
                    logger.info(
                        f"Отправил напоминание о продлении заказа/подписки №{order.id}"
                    )
                await session.commit()

    except Exception as e:
        logger.exception(e)
        await broadcast(
            bot,
            [config.tg_bot.developer_id],
            f"Ошибка во время уведомления о заканчивающемся заказе/подписке: {str(e)}",
        )


async def archiving_expired_orders(
    bot: Bot, session_factory: sessionmaker, config: Settings, fluent: Localizator
) -> None:
    try:
        async with session_factory() as session:
            bot_settings = await get_bot_settings(session)
            if bot_settings and bot_settings.order_archiving_status:
                success = 0
                order_ids = await get_expired_order_ids(session)
                for order_id in order_ids:
                    logger.info(f"Архивирую заказ/подписку №{order_id}")

                    order = await get_order(session, order_id, joined=True)
                    user = await get_user(session, order.user.telegram_id, joined=True)

                    locale: TranslatorRunner = fluent.get_by_locale(
                        order.user.lang_code
                    )

                    allowed_ip = await get_allowed_ip_by_order(
                        session, user.telegram_id, order.id
                    )

                    if len(order.proxies) > 0:
                        old_proxies_creds = [
                            {
                                "server_id": proxy.server_id,
                                "login": proxy.login,
                                "password": proxy.password,
                                "reload_link": proxy.reload_link,
                                "port_https": proxy.port_https,
                            }
                            for proxy in order.proxies
                        ]

                        await change_expired_proxy(session, order.proxies)

                        server_proxies = await get_server_proxies(
                            session, old_proxies_creds, order.proxies
                        )

                        user.proxies = [
                            proxy
                            for proxy in user.proxies
                            if proxy not in order.proxies
                        ]

                        for server, proxies in server_proxies.items():
                            await change_expired_proxies(server, proxies, allowed_ip)

                    order.status = "archive"
                    order.allowed_ips.clear()
                    order.proxies.clear()

                    if order.type == "vpn":
                        if await delete_vpn_config(order.vpn_server, user.telegram_id):
                            logger.info(f"Удалил впн конфиг юзера {user.telegram_id}")
                            order.vpn_server = None

                    if order.type == "subscription":
                        text = locale.get("subscription-expired")
                    elif order.type == "vpn":
                        text = locale.get("vpn-expired")
                    else:
                        text = locale.get("order-expired", order_id=str(order.id))

                    logger.info(f"Архивировал заказ/подписку/впн №{order_id}")
                    await session.commit()
                    await broadcast(bot, [user.telegram_id], text)
                    success += 1

                if len(order_ids) > 0:
                    logger.info(
                        f"Архивировал успешно {success} из {len(order_ids)} заказ(ов)/подписок/впн"
                    )

    except Exception as e:
        logger.exception(e)
        await broadcast(
            bot,
            [config.tg_bot.developer_id],
            f"Ошибка во время архивирования заказов/подписок/впнов: {str(e)}",
        )


async def change_authorization_type_job(
    bot: Bot,
    locale: Locale,
    session_factory: sessionmaker,
    user_id: int,
    order_id: int,
    allowed_ip_id: int,
    authorization_type: str,
    config: Settings,
) -> None:
    try:
        async with session_factory() as db_session:
            order = await get_order(db_session, order_id, joined=True)

            old_ip = await get_allowed_ip_by_order(db_session, user_id, order_id)

            if old_ip:
                if authorization_type == "by_password":
                    await remove_ip_from_order(db_session, order, old_ip)

            ip_address = None
            if allowed_ip_id:
                ip_address = await get_ip(db_session, allowed_ip_id)
                if old_ip:
                    # Меняем старый разрешенный ip на новый
                    # Каждый юзер может добавить только 1 ip к заказу
                    await remove_ip_from_order(db_session, order, old_ip)
                    await add_ip_to_order(db_session, order, ip_address)
                else:
                    await add_ip_to_order(db_session, order, ip_address)
                    # Добавляем разрешенный ip к заказу

            server_proxies = await get_server_proxies(db_session, order.proxies)

            for server, proxies in server_proxies.items():
                await change_authorization_type(
                    server, proxies, authorization_type, old_ip, ip_address
                )
                await update_authorization_type(db_session, proxies, authorization_type)

        if order.type == "subscription":
            msg = locale.get("subscription-authorization-change-success")
        else:
            msg = locale.get("authorization-change-success", order_id=str(order.id))
        await broadcast(
            bot,
            [user_id],
            msg,
        )

        logger.info(f"Изменена авторизация заказа №{order.id}")

    except Exception as e:
        logger.exception(e)
        if order.type == "subscription":
            msg = locale.get("subscription-change-authorization-error")
        else:
            msg = locale.get("change-authorization-error", order_id=str(order.id))
        await broadcast(bot, [user_id], msg)
        await broadcast(
            bot,
            [config.tg_bot.developer_id],
            f"Ошибка во время изменения авторизации: {str(e)}",
        )


async def update_rotation_type(
    bot: Bot,
    locale: Locale,
    session_factory: sessionmaker,
    user_id: int,
    order_id: int,
    rotation_period: int,
    reload_type: str,
    config: Settings,
) -> None:

    try:
        async with session_factory() as db_session:
            order = await get_order(db_session, order_id, joined=True)

            server_proxies = await get_server_proxies(db_session, order.proxies)

            for server, proxies in server_proxies.items():
                await change_rotation_type(
                    server, proxies, rotation_period, reload_type
                )

            await update_rotation_period(
                db_session, order.proxies, rotation_period, reload_type
            )

        if order.type == "subscription":
            msg = locale.get("subscription-rotation-change-success")
        else:
            msg = locale.get("rotation-change-success", order_id=str(order.id))
        await broadcast(bot, [user_id], msg)

        logger.info(f"Изменена ротация заказа/подписки №{order.id}")

    except Exception as e:
        logger.exception(e)
        if order.type == "subscription":
            msg = locale.get("subscription-change-rotation-error")
        else:
            msg = locale.get("change-rotation-error", order_id=str(order.id))
        await broadcast(bot, [user_id], msg)
        await broadcast(
            bot,
            [config.tg_bot.developer_id],
            f"Ошибка во время изменения ротации: {str(e)}",
        )


async def check_proxies(
    bot: Bot, session_factory: sessionmaker, config: Settings
) -> None:
    async with session_factory() as session:
        bot_settings = await get_bot_settings(session)
        if bot_settings and bot_settings.proxy_checker_status:
            logger.info("Начинаю проверку всех прокси")
            all_proxies = await get_all_proxies(session)
            sem = asyncio.Semaphore(6)
            tasks = [
                asyncio.ensure_future(check_proxy(sem, proxy, bot, session, config))
                for proxy in all_proxies
            ]
            await asyncio.gather(*tasks)

            if len(all_proxies) > 0:
                logger.info(f"Проверил {len(all_proxies)} прокси")


async def check_proxy(
    sem: asyncio.Semaphore,
    proxy: Proxy,
    bot: Bot,
    session: AsyncSession,
    config: Settings,
) -> None:
    try:
        async with sem:
            await asyncio.sleep(randint(1, 4))
            logger.info(
                f"Проверяю прокси {proxy.login}:{proxy.password}@{proxy.server.ip_address}:{proxy.port_https} с сервера №{proxy.server_id}"
            )
            proxy_status, error_msg = await get_proxy_status(proxy)
            if error_msg:
                if proxy.status != "sold":
                    proxy.status = "invalid"
                await broadcast(
                    bot,
                    config.tg_bot.admin_ids,
                    error_msg,
                )
            else:
                if proxy.status != "sold":
                    proxy.status = "available"
            await session.commit()

    except Exception as e:
        logger.exception(e)
        await broadcast(
            bot,
            [config.tg_bot.developer_id],
            f"Ошибка во время проверки прокси: {str(e)}",
        )


async def change_proxy(
    bot: Bot,
    locale: Locale,
    session_factory: sessionmaker,
    user_id: int,
    order_id: int,
    proxy_id: int,
    new_proxy_id: int,
    config: Settings,
    country_id: int = None,
    provider_id: int = None,
):
    try:
        async with session_factory() as session:
            order = await get_order(session, order_id, joined=True)
            user = await get_user(session, order.user.telegram_id, joined=True)
            old_proxy = await get_proxy(session, proxy_id)
            new_proxy = await get_proxy(session, new_proxy_id)

            try:
                order.proxies.remove(old_proxy)
                user.proxies.remove(old_proxy)
            except ValueError:
                pass

            allowed_ip = await get_allowed_ip_by_order(
                session, user.telegram_id, order.id
            )

            logger.info(
                f"Меняю прокси в заказе №{order_id} c {old_proxy} на {new_proxy}"
            )

            if old_proxy:
                old_proxies_creds = [
                    {
                        "server_id": old_proxy.server_id,
                        "login": old_proxy.login,
                        "password": old_proxy.password,
                        "reload_link": old_proxy.reload_link,
                        "port_https": old_proxy.port_https,
                    }
                ]

                await change_expired_proxy(session, [old_proxy])

                server_proxies = await get_server_proxies(
                    session, old_proxies_creds, [old_proxy]
                )

                for server, proxies in server_proxies.items():
                    await change_expired_proxies(server, proxies, allowed_ip)

            order.proxies.append(new_proxy)
            user.proxies.append(new_proxy)
            new_proxy.status = "sold"

            if country_id:
                order.country_id = country_id
            if provider_id:
                order.provider_id = provider_id

            await session.commit()

            await broadcast(
                bot,
                [user_id],
                locale.get(
                    "new-proxy-msg",
                    login=new_proxy.login,
                    password=new_proxy.password,
                    ip_address=new_proxy.server.ip_address,
                    port=new_proxy.port_https,
                ).replace("\xa0", ""),
            )
    except Exception as e:
        logger.exception(e)
        await broadcast(
            bot,
            [config.tg_bot.developer_id],
            f"Ошибка во время изменения прокси в заказе/подписке: {str(e)}",
        )
        await broadcast(bot, [user_id], locale.get("change-proxy-error"))


async def check_proxy_speed_job(
    bot: Bot,
    locale: Locale,
    session_factory: sessionmaker,
    user_id: int,
    proxy_id: int,
    config: Settings,
):
    try:
        async with session_factory() as session:
            proxy = await get_proxy(session, proxy_id)
            logger.info(f"Проверяю скорость работы прокси {proxy}")
            os.environ[
                "HTTP_PROXY"
            ] = f"http://{proxy.login}:{proxy.password}@{proxy.server.ip_address}:{proxy.port_https}"
            os.environ[
                "HTTPS_PROXY"
            ] = f"http://{proxy.login}:{proxy.password}@{proxy.server.ip_address}:{proxy.port_https}"
            os.environ[
                "http_proxy"
            ] = f"http://{proxy.login}:{proxy.password}@{proxy.server.ip_address}:{proxy.port_https}"
            os.environ[
                "https_proxy"
            ] = f"http://{proxy.login}:{proxy.password}@{proxy.server.ip_address}:{proxy.port_https}"

            st = await asyncio.to_thread(speedtest.Speedtest)
            await asyncio.to_thread(st.get_best_server)
            download_speed = round(await asyncio.to_thread(st.download) / 1e6, 2)
            upload_speed = round(await asyncio.to_thread(st.upload) / 1e6, 2)
            await broadcast(
                bot,
                [user_id],
                locale.get(
                    "proxy-speed-msg",
                    download_speed=download_speed,
                    upload_speed=upload_speed,
                ),
            )
    except speedtest.ConfigRetrievalError as e:
        await broadcast(
            bot,
            [config.tg_bot.developer_id],
            f"Ошибка во время проверки скорости прокси: {e.__repr__()}",
        )
        await broadcast(bot, [user_id], locale.get("speed-check-error"))
    except Exception as e:
        logger.exception(e)
        await broadcast(
            bot,
            [config.tg_bot.developer_id],
            f"Ошибка во время проверки скорости прокси: {e.__repr__()}",
        )
        await broadcast(bot, [user_id], locale.get("speed-check-error"))


async def generate_vpn_config_job(
    bot: Bot,
    server: Server,
    locale: Locale,
    telegram_id: int,
    order_id: int,
    config: Settings,
) -> None:
    try:
        vpn_config = await generate_vpn_config(server, telegram_id)
        qr_code_file_path = f"app/media/{telegram_id}_{order_id}_vpn_qr.png"
        config_file_path = f"app/media/{telegram_id}_{order_id}_vpn.conf"

        await asyncio.to_thread(
            generate_qr_code, config_content=vpn_config, file_path=qr_code_file_path
        )

        async with aiofiles.open(config_file_path, "w") as f:
            await f.write(vpn_config)

        logger.info(f"Сгенерировал QR код для юзера {telegram_id}")
        await send_document(
            bot,
            telegram_id,
            FSInputFile(config_file_path),
            caption=locale.get("vpn-config-msg"),
        )
        await send_document(
            bot,
            telegram_id,
            FSInputFile(qr_code_file_path),
            caption=locale.get("qr-code-msg"),
        )

    except Exception as e:
        logger.exception(e)
        await broadcast(bot, [telegram_id], locale.get("vpn-setup-error"))
        await broadcast(
            bot,
            [config.tg_bot.developer_id],
            f"Ошибка во время генерации QR кода для впн: {str(e)}",
        )
